package com.jas.logic;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.TreeMap;

import com.jas.common.Action;
import com.jas.common.CommandFormatException;
import com.jas.common.DateFormatException;
import com.jas.common.DeadlineEvent;
import com.jas.common.Event;
import com.jas.common.EventDateException;
import com.jas.common.FloatingEvent;
import com.jas.common.JasTimer;
import com.jas.common.Language;
import com.jas.common.Notification;
import com.jas.common.TimedEvent;
import com.jas.common.Utilities;
import com.jas.storage.StorageAPI;
import com.jas.storage.StorageException;

public class Logic {

	protected enum KeywordType {
		DESCRIPTION, START, END, SET
	}

	private ArrayList<Event> listOfEvents;
	private Action currAction;
	private ArrayList<Notification> listOfNotification;
	
	// @author a0118677r
	public Logic() {
		try {
			listOfEvents = (ArrayList<Event>) StorageAPI.loadEventList();
			listOfNotification = (ArrayList<Notification>) StorageAPI
					.loadNotificationList();
			startNotificationTimer();
		} catch (StorageException e) {
			listOfEvents = new ArrayList<Event>();
		}
	}

	//@author a0118677r
	protected ArrayList<Event> add(String originalText,TreeMap<KeywordType, String> mappingOfKeywordTypesToParameters)
			throws CommandFormatException, StorageException,
			DateFormatException, EventDateException {

		Event newEvent = createNewEvent(originalText,mappingOfKeywordTypesToParameters);
		listOfEvents.add(newEvent);
		
		// @author A0121310J
		//Add to currAction for backup
		currAction = new Action(Action.ActionType.ADD,newEvent);
		//
		

		try {
			StorageAPI.storeEventList(listOfEvents);
			ArrayList<Event> listToReturn = new ArrayList<Event>();
			listToReturn.add(newEvent);
			return listToReturn;
		} catch (StorageException e) {
			listOfEvents.remove(listOfEvents.size() - 1);
			throw e;
		}
	}

	// @author A0121310J
	// for add to eventList if the Event is already created
	/* probably it is the same as the above class, but for simplification, 
	 * creating a new method for adding existed Event will be more convenient :) */
	private ArrayList<Event> add(Event event)
			throws CommandFormatException, StorageException,
			DateFormatException, EventDateException {

		listOfEvents.add(event);
		
		// @author A0121310J
		//Add to currAction for backup
		currAction = new Action(Action.ActionType.ADD,event);
		//

		try {
			StorageAPI.storeEventList(listOfEvents);
			ArrayList<Event> listToReturn = new ArrayList<Event>();
			listToReturn.add(event);
			return listToReturn;
		} catch (StorageException e) {
			listOfEvents.remove(listOfEvents.size() - 1);
			throw e;
		}
	}
	
	//@author a0118677r
	private Event createNewEvent(String originalText,TreeMap<KeywordType, String> mappingOfKeywordTypesToParameters)
			throws CommandFormatException, DateFormatException,
			EventDateException {

		Event newEvent;
		String checkStartDateValidity, checkEndDateValidity;
		String description;
		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.DESCRIPTION)) {
			description = mappingOfKeywordTypesToParameters.get(KeywordType.DESCRIPTION);
		} else {
			throw new CommandFormatException(Language.getString("ERROR_COMMAND_MUST_HAVE_DESCRIPTION"));
		}
		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.END)) {
			String endDateTimeString = mappingOfKeywordTypesToParameters
					.get(KeywordType.END);
			checkEndDateValidity = Utilities.getDateFormat(endDateTimeString);

			
			if (checkEndDateValidity.isEmpty()) {
				throw new CommandFormatException(
						Language.getString("ERROR_WRONG_DATE_FORMAT"));
			}
			Calendar eventEndDateTime = Utilities.stringToCalendar(
					endDateTimeString, checkEndDateValidity);

			if (mappingOfKeywordTypesToParameters
					.containsKey(KeywordType.START)) {
				String startDateTimeString = mappingOfKeywordTypesToParameters
						.get(KeywordType.START);
				
				checkStartDateValidity = Utilities
						.getDateFormat(startDateTimeString);
				if (checkStartDateValidity.isEmpty()) {
					throw new CommandFormatException(
							Language.getString("ERROR_WRONG_DATE_FORMAT"));
				}
				Calendar eventStartDateTime = Utilities.stringToCalendar(
						startDateTimeString, checkStartDateValidity);

				newEvent = new TimedEvent(originalText, eventStartDateTime,
						eventEndDateTime);
				return newEvent;
			} else {
				newEvent = new DeadlineEvent(originalText, eventEndDateTime);
				return newEvent;
			}
		} else {
			newEvent = new FloatingEvent(originalText);
			return newEvent;
		}
		
	}

	//@author a0118677r
	public ArrayList<Event> update(String eventId,
			TreeMap<KeywordType, String> mappingOfKeywordTypesToParameters)
			throws StorageException, DateFormatException, EventDateException {

		int indexOfEventWithId = getIndexOfEventWithId(eventId);
		Event eventToUpdate = listOfEvents.get(indexOfEventWithId);
		Event backupOfEventToBeUpdated = eventToUpdate.copy();
		
		// @author A0121310J
		// Add to currAction for backup purpose
		currAction = new Action(Action.ActionType.UPDATE,backupOfEventToBeUpdated);
		// end
		
		if (mappingOfKeywordTypesToParameters
				.containsKey(KeywordType.DESCRIPTION)) {
			String description = mappingOfKeywordTypesToParameters
					.get(KeywordType.DESCRIPTION);
			eventToUpdate.setEventDescription(description);
		}
		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.SET)) {
			String setParameter = mappingOfKeywordTypesToParameters.get(KeywordType.SET);
			if (setParameter.equals(Language.getString("KEYWORD_COMPLETED"))) {
				eventToUpdate.setCompleted(true);
			} else if (setParameter.equals(Language.getString("KEYWORD_UNCOMPLETED"))) {
				eventToUpdate.setCompleted(false);
			}
		}
		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.END)) {
			String endDateTimeString = mappingOfKeywordTypesToParameters
					.get(KeywordType.END);
			Calendar endDateTime = Utilities.stringToCalendar(
					endDateTimeString, Language.getString("DATE_FORMAT"));
			if (eventToUpdate instanceof TimedEvent) {
				((TimedEvent) eventToUpdate).setEventEndDateTime(endDateTime);
			} else if (eventToUpdate instanceof DeadlineEvent) {
				((DeadlineEvent) eventToUpdate)
						.setEventEndDateTime(endDateTime);
			} else if (eventToUpdate instanceof FloatingEvent){
				eventToUpdate = ((FloatingEvent) eventToUpdate).convertToDeadlineEvent(endDateTime);
				listOfEvents.remove(indexOfEventWithId);
				listOfEvents.add(indexOfEventWithId, eventToUpdate);
			}
		}
		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.START)) {
			String startDateTimeString = mappingOfKeywordTypesToParameters
					.get(KeywordType.START);
			Calendar startDateTime = Utilities.stringToCalendar(
					startDateTimeString, Language.getString("DATE_FORMAT"));
			if (eventToUpdate instanceof TimedEvent) {
				((TimedEvent) eventToUpdate)
						.setEventStartDateTime(startDateTime);
			} else if (eventToUpdate instanceof DeadlineEvent){
				eventToUpdate = ((DeadlineEvent) eventToUpdate).convertToTimedEvent(startDateTime);
				listOfEvents.remove(indexOfEventWithId);
				listOfEvents.add(indexOfEventWithId, eventToUpdate);
			} else {
				throw new EventDateException(Language.getString("ERROR_CAN_NOT_SET_ONLY_START"));
			}
		}

		try {
			StorageAPI.storeEventList(listOfEvents);
			ArrayList<Event> eventToReturn = new ArrayList<Event>();
			eventToReturn.add(eventToUpdate);
			return eventToReturn;
		} catch (StorageException e) {
			listOfEvents.remove(indexOfEventWithId);
			listOfEvents.add(indexOfEventWithId, backupOfEventToBeUpdated);
			throw e;
		}
	}
	
	//@author a0118677r
	public ArrayList<Event> display(
			TreeMap<KeywordType, String> mappingOfKeywordTypesToParameters)
			throws CommandFormatException, DateFormatException {

		if (mappingOfKeywordTypesToParameters.containsKey(KeywordType.START)
				&& mappingOfKeywordTypesToParameters
						.containsKey(KeywordType.END)) {
			String fromDateTimeString = mappingOfKeywordTypesToParameters
					.get(KeywordType.START);

			String toDateTimeString = mappingOfKeywordTypesToParameters
					.get(KeywordType.END);
			String userDateInputFormatForStartDate = Utilities
					.getDateFormat(fromDateTimeString);
			String userDateInputFormatForEndDate = Utilities
					.getDateFormat(toDateTimeString);

			if (userDateInputFormatForStartDate.isEmpty()
					|| userDateInputFormatForEndDate.isEmpty()) {
				throw new CommandFormatException(
						Language.getString("ERROR_WRONG_DATE_FORMAT"));
			}

			Calendar from = Utilities.stringToCalendar(fromDateTimeString,
					userDateInputFormatForStartDate);
			Calendar to = Utilities.stringToCalendar(toDateTimeString,
					userDateInputFormatForEndDate);
			if (from.after(to)) {
				throw new CommandFormatException(
						Language.getString("ERROR_END_BEFORE_START"));
			}

			return display(from, to);
		} else if (mappingOfKeywordTypesToParameters
				.containsKey(KeywordType.DESCRIPTION)) {
			String displayParameterString = mappingOfKeywordTypesToParameters
					.get(KeywordType.DESCRIPTION);
			return display(displayParameterString);
		} else {

			throw new CommandFormatException(
					Language.getString("ERROR_INVALID_NUMBER_OF_PARAMETERS"));
		}
	}

	//@author a0118677r
	public ArrayList<Event> display(Calendar from, Calendar to) {
		ArrayList<Event> listToReturn = new ArrayList<Event>();
		for (Event event : listOfEvents) {
			if (event instanceof TimedEvent) {
				if (((TimedEvent) event).getEventEndDateTime().after(from)
						&& ((TimedEvent) event).getEventEndDateTime()
								.before(to)) {
					listToReturn.add(event);
				} else if (((TimedEvent) event).getEventStartDateTime().after(
						from)
						&& ((TimedEvent) event).getEventStartDateTime().before(
								to)) {
					listToReturn.add(event);
				}
			} else if (event instanceof DeadlineEvent) {
				if (((DeadlineEvent) event).getEventEndDateTime().after(from)
						&& ((DeadlineEvent) event).getEventEndDateTime()
								.before(to)) {
					listToReturn.add(event);
				}
			}
		}
		return listToReturn;
	}

	//@author a0118677r
	private ArrayList<Event> display(String... searchText)
			throws DateFormatException {
		
		Calendar from = getCalendarRepresentingTodayMidnight();
		Calendar to = getCalendarRepresentingTodayMidnight();
		
		switch (searchText[0].toLowerCase()) {
		case "all":
			return displayAllEvents();
		case "today":

			return display(from, to);
		case "tomorrow":

			to.add(Calendar.DAY_OF_YEAR, 1);
			return display(from, to);
		case "date":
			to = Utilities.stringToCalendar(searchText[0],
					Language.getString("SEARCH_DATE_FORMAT"));
			return display(from, to);
		case "datetime":
			to = Utilities.stringToCalendar(searchText[0],
					Language.getString("SEARCH_DATE_TIME_FORMAT"));
			return display(from, to);
		case "week":
			to.add(Calendar.DAY_OF_YEAR, 7);
			return display(from, to);
		case "month":

			from = to = Utilities.stringToCalendar(searchText[0],
					Language.getString("SEARCH_MONTH_FORMAT"));
			to = Utilities.stringToCalendar(searchText[0],
					Language.getString("SEARCH_MONTH_FORMAT"));
			return display(from, to);
		case "completed":
			return displayCompleted();
		case "uncompleted":
			return displayUncompleted();
		default:

			String eventId = searchText[0];
			ArrayList<Event> eventToDisplay = new ArrayList<Event>();
			int indexOfEventWithId = getIndexOfEventWithId(eventId);

			if (indexOfEventWithId != -1) {
				assert !(indexOfEventWithId < 0);

				eventToDisplay.add(listOfEvents.get(indexOfEventWithId));
			}

			return eventToDisplay;
		}

	}

	//@author a0118677r
	private Calendar getCalendarRepresentingTodayMidnight() {
		Calendar todayMidnight = Calendar.getInstance();
		todayMidnight.set(Calendar.HOUR_OF_DAY, 0);
		todayMidnight.set(Calendar.MINUTE, 0);
		todayMidnight.set(Calendar.SECOND, 0);
		return todayMidnight;
	}

	//@author a0118677r
	/**
	 * This method searches storage for completed events.
	 * 
	 * @param searchText
	 *            User text.
	 * @return Returns a list unfinished events.
	 */
	protected ArrayList<Event> displayCompleted() {
		ArrayList<Event> resultList = new ArrayList<Event>();
		for(Event event:listOfEvents){
			if(event.isCompleted()){
				resultList.add(event);
			}
		}
		return resultList;
	}

	//@author a0118677r
	/**
	 * This method searches storage for unfinished events.
	 * 
	 * @param searchText
	 *            User text.
	 * @return Returns a list unfinished events.
	 */
	public ArrayList<Event> displayUncompleted() {
		ArrayList<Event> resultList = new ArrayList<Event>();
		for(Event event:listOfEvents){
			if(!event.isCompleted()){
				resultList.add(event);
			}
		}
		return resultList;
	}

	//@author a0118677r
	/**
	 * This method displays all events stored by the system.
	 * 
	 * @return Returns a list of events stored by the system.
	 */
	public ArrayList<Event> displayAllEvents() {
		return listOfEvents;
	}

	//@author a0118677r
	public ArrayList<Event> delete(String eventId) throws StorageException,
			EventIdException {

		int indexOfEventWithId = getIndexOfEventWithId(eventId);

		if (indexOfEventWithId != -1) {
			assert !(indexOfEventWithId < 0);

			Event tempCopy = listOfEvents.get(indexOfEventWithId);
			
			//@author A0121310J
			//Add to currAction for backup purpose
			currAction = new Action(Action.ActionType.DELETE,tempCopy);
			//end
			
			listOfEvents.remove(indexOfEventWithId);

			try {
				ArrayList<Event> eventToReturn = new ArrayList<Event>();
				StorageAPI.storeEventList(listOfEvents);
				eventToReturn.add(tempCopy);
				return eventToReturn;
			} catch (StorageException e) {
				listOfEvents.add(tempCopy);
				throw e;
			}
		} else {
			throw new EventIdException(String.format(
					Language.getString("ERROR_WRONG_INDEX"), eventId));
		}

	}

	//@author a0118677r
	public ArrayList<Event> deleteAll() throws StorageException {
		ArrayList<Event> tempCopy = new ArrayList<Event>(listOfEvents);
		listOfEvents.clear();
		try {
			StorageAPI.storeEventList(new ArrayList<Event>());
			return tempCopy;
		} catch (StorageException e) {
			listOfEvents.addAll(tempCopy);
			throw e;
		}
	}

	
	// @author A0121310J
	// undo for 3 case - ADD, DELETE and UPDATE
	public void undo(){
		switch(currAction.getActionDesc()){
		//If the previous action is add, delete it 
		case "ADD":
			try {
				delete(currAction.getEvent().getEventId());
			} catch (StorageException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (EventIdException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			break;
		//end add
			
			
		//If the previous action is delete, add back it
		case "DELETE": 
			try {
				add(currAction.getEvent());
			} catch (CommandFormatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (StorageException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (DateFormatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (EventDateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			break;
		//end delete	
			
		//If the previous action is update, restore the previous setting
		case "UPDATE": 
			//update()
			
			break;
		}
		//end update
	}
	
	
	//@author a0118677r
	private int getIndexOfEventWithId(String eventId) {
		for (Event event : listOfEvents) {
			if (event.getEventId().equalsIgnoreCase(eventId)) {
				return listOfEvents.indexOf(event);
			}
		}
		return -1;
	}

	//@author A0098803W
		/**
		 * This method returns the number of events currently stored in storage.
		 * 
		 * @return
		 */
		protected int getTotalNumberOfEvents() {
			return listOfEvents.size();
		}

		protected ArrayList<Event> search(String sentence)
				throws DateFormatException {

			ArrayList<Event> sortedEventsList = new ArrayList<Event>();
			sortedEventsList = searchDescription(sentence);

			return sortedEventsList;
		}

		//@author A0098803W
		/**
		 * This method searches the event description.
		 * 
		 * @param searchText
		 *            The search text entered by the user.
		 * @return List of events that matches the search text or an empty list if
		 *         no such event is found.
		 * @throws DateFormatException
		 */
		private ArrayList<Event> searchDescription(String searchText)
				throws DateFormatException {
			ArrayList<Event> matchingList = new ArrayList<Event>();

			searchText = searchText.trim();

			matchingList = performSearchForEventsByEventDescription(searchText);

			matchingList = Utilities.sortList(matchingList,
					Language.getString("SHOW_ASCENDING"));
			return matchingList;
		}

		//@author A0098803W
		/**
		 * This method searches storage for events based on event description.
		 * 
		 * @param searchText
		 *            User text.
		 * @return Returns a list of events that matches the event description
		 *         entered by the user.
		 */
		private ArrayList<Event> performSearchForEventsByEventDescription(
				String searchText) {
			ArrayList<Event> matchingList = new ArrayList<Event>();

			for (Event event : listOfEvents) {

				String searchTextToLowerCase = searchText.toLowerCase();
				String descriptionToLowerCase = event.getEventDescription()
						.toLowerCase();
				if (descriptionToLowerCase.indexOf(searchTextToLowerCase) != -1) {

					matchingList.add(event);
				}
			}
			return matchingList;
		}

		//@author A0098803W
		/**
		 * This method starts the timer that monitors the notification storage file.
		 */
		private void startNotificationTimer() {
			Runnable task = new JasTimer();
			Thread worker = new Thread(task);
			worker.start();
		}

		//@author A0098803W
		/**
		 * This add method adds a new notification to the notification storage file.
		 * 
		 * @param date
		 *            User input date.
		 * @param dateFormat
		 *            User input date format.
		 * @param message
		 *            The message to send.
		 * @return
		 * @throws CommandFormatException
		 * @throws StorageException
		 * @throws DateFormatException
		 */
		protected Notification add(String date, String dateFormat, String message)
				throws CommandFormatException, StorageException,
				DateFormatException {

			Notification newNotification = new Notification();
			newNotification.setMessage(message);
			String userDateFormat = Utilities.getDateFormat(date);
			if (userDateFormat.isEmpty()) {
				throw new DateFormatException(
						Language.getString("ERROR_WRONG_DATE_FORMAT"));
			}
			Calendar userCalendar = Utilities
					.stringToCalendar(date, userDateFormat);
			newNotification.setTimeToNotify(userCalendar);
			listOfNotification.add(newNotification);

			try {
				StorageAPI.storeNotificationList(listOfNotification);

				return newNotification;
			} catch (StorageException e) {
				listOfEvents.remove(listOfEvents.size() - 1);
				throw e;
			}
		}

		//@author A0098803W
		/**
		 * This method deletes the recently entered notification.
		 * 
		 * @param notificationObj
		 *            The <code>Notification</code> object.
		 * @return Returns the recently removed <code>Notification</code> object.
		 * @throws StorageException
		 */
		protected Notification delete(Notification notificationObj)
				throws StorageException {

			int sizeOfNotificationListBeforeDelete = listOfNotification.size();
			listOfNotification.remove(notificationObj);
			assert (sizeOfNotificationListBeforeDelete > listOfNotification.size());
			StorageAPI.storeNotificationList(listOfNotification);
			return notificationObj;

		}

		//@author A0098803W
		/**
		 * This method updates the messaging gateway for the program.
		 * 
		 * @param url
		 *            Gateway URL.
		 * @throws CommandFormatException
		 * @throws StorageException
		 */
		protected void updateGateway(String url) throws CommandFormatException,
				StorageException {
			String checkValidUrl = Utilities.validateGatewayURL(url);
			if (checkValidUrl.isEmpty()) {
				throw new CommandFormatException(
						Language.getString("ERROR_WRONG_URL_FORMAT"));
			} else {
				StorageAPI.storeGatewaySettings(checkValidUrl);
			}
		}
		
}
